package controller;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import model.OOAttribute;
import model.OOClass;
import model.OOMethod;
import model.OOModel;
import model.OOPackage;

public class ModelGenerator {

	private ArrayList<TokensClass> arrTypes = new ArrayList<TokensClass>();
	private ArrayList<TokensClass> arrReturnTypes = new ArrayList<TokensClass>();
	private ArrayList<TokensClass> arrAccessModifiers = new ArrayList<TokensClass>();
	private ArrayList<OOPackage> arrPkgs = new ArrayList<OOPackage>();
	
	private OOModel ooModel;
	public ModelGenerator(OOModel ooModel)
	{
		this.ooModel=ooModel;
		arrTypes.add(new TokensClass(0, 0, "", "INT"));
		arrTypes.add(new TokensClass(0, 0, "", "DOUBLE"));
		arrTypes.add(new TokensClass(0, 0, "", "LONG"));
		arrTypes.add(new TokensClass(0, 0, "", "FLOAT"));
		arrTypes.add(new TokensClass(0, 0, "", "SHORT"));
		arrTypes.add(new TokensClass(0, 0, "", "IDENTIFIER"));
		arrReturnTypes.addAll(arrTypes);
		arrReturnTypes.add(new TokensClass(0, 0, "", "VOID"));

		arrAccessModifiers.add(new TokensClass(0, 0, "", "PUBLIC"));
		arrAccessModifiers.add(new TokensClass(0, 0, "", "PRIVATE"));
		arrAccessModifiers.add(new TokensClass(0, 0, "", "PROTECTED"));
	}

	private enum CodeStage
	{
		START,IMPORTS,CLASS_DEF,METHOD_DEF,METHOD_INSIDE,ATTRIBUTE_DEF
	}

	public void generateModel(ArrayList<TokensClass> arrToken)//:D
	{
		OOClass ooClass = null;
		int openBraces=0,methodOpenBraceCount=0;
		CodeStage currentStage = CodeStage.START;
		OOPackage ooPackage;
		ArrayList<ArrayList<TokensClass>> subArrays;
		subArrays = TokenParser.parse(arrToken);//Parse the tokensObjects ArrayList into subArrays
		for (ArrayList<TokensClass> subArray : subArrays) 
		{
			if(currentStage==CodeStage.START)
			{
				if(subArray.get(0).getType()=="PACKAGE")
				{
					ooPackage= new OOPackage();
					//ooPackage.setName(subArray.get(1).getName());
					String logicalPath="";
					// we loop to get the path of the pkg till end of subArray
					// note: we don't need neither token no.1 "package" nor last token ";" 
					for(int i=1;i<subArray.size()-1;i++)
						logicalPath+=subArray.get(i);
						
					ooPackage.setLogicalPath(logicalPath);
					arrPkgs.add(ooPackage);
					continue;
				}
			}
			// if we haven't already detected the class definition
			if(ooClass==null) // denotes that currentStage != CLASS_DEF
			{
				openBraces+= countBraceCountDifference(subArray);
				ooClass=detectClass(subArray);
				if(ooClass!=null)
				{
					ooModel.classes.add(ooClass);
					ooClass.isLinked=true;
					currentStage=CodeStage.CLASS_DEF;
				}
			}
			else if(currentStage==CodeStage.METHOD_DEF ||
					currentStage==CodeStage.METHOD_INSIDE) // we are inside a method
			{
				methodOpenBraceCount+= countBraceCountDifference(subArray);
				openBraces+= countBraceCountDifference(subArray);
				if(methodOpenBraceCount!=0)
				{
					currentStage =CodeStage.METHOD_INSIDE;
					ooClass.getMethodAt(ooClass.getMethodsCount()-1).code+=getMethodCode(subArray);
				}
				else
				{
					currentStage=CodeStage.CLASS_DEF;
					// expect Right brace that closes the class code
					mds.allowedNextTokens.add(new TokensClass(0, 0, "", "RBRACE"));
				}
			}
			else if(currentStage==CodeStage.CLASS_DEF)
			{
				// TODO: we need to support inner classes
				//openBraces+= countBraceCountDifference(subArray);
				OOMethod ooMethod = detectMethod(subArray);
				if(ooMethod!=null)
				{
					ooClass.addMethod(ooMethod);
					currentStage=CodeStage.METHOD_DEF;
					methodOpenBraceCount+= countBraceCountDifference(subArray);
					openBraces+= countBraceCountDifference(subArray);
					//currentStage=CodeStage.ATTRIBUTE_DEF;
				}

				else  // if it is not a method, then it is an attribute!
				{
					OOAttribute ooAttribute=detectAttribute(subArray);
					if(ooAttribute!= null)
					{
						ooClass.addVariable(ooAttribute);
					}


				}
				// if we are done with the class code
				if(mds.currentToken.getType().equals("RBRACE"))
					System.out.println("Done with class code!\n");
			}
		}
		linkModel(); // link classes together
	}

	private int countBraceCountDifference(ArrayList<TokensClass> arr)
	{
		int countOpen=0,countClose=0;
		for(TokensClass token: arr)
		{
			if(token.getName().equals("{"))
				countOpen++;
			if(token.getName().equals("}"))
				countClose++;
		}
		return countOpen-countClose;
	}

	private String getMethodCode(ArrayList<TokensClass> arr)
	{
		String str = "";
		for(TokensClass token: arr)
		{
			str+=token.getName();
		}
		return str;
	}


	private OOClass detectClass(ArrayList<TokensClass> declarationTokens){
		OOClass class1 = new OOClass();
		class1.setAccessModifier(getAccessModifier(declarationTokens));
		Map<String,Boolean> classOrInterface = determineWhetherItsClassOrInterFace(declarationTokens);//associative array used for ease of maintenance
		if(classOrInterface.get("itIsAClass")){
			detectClassExplicitly(declarationTokens, class1);//pass by reference
			return class1;
		}else if(classOrInterface.get("itIsAnInterface")){
			detectInterfaceExplicitly(declarationTokens, class1);//pass by reference
			return class1;
		}
		else {//neither class nor interface
			return null;
		}
	}
	private void detectClassExplicitly(ArrayList<TokensClass> declarationTokens,OOClass class1){
		switch (getEnumForFoundKeyWord(declarationTokens)){
		case _final:{
			class1.setIsFinal(true);
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _static:{
				class1.setIsStatic(true);
				switch(getEnumForFoundKeyWord(declarationTokens)){
				case _class:
					afterClass(declarationTokens,class1);
					break;
				}
				break;
			}
			case _class:{
				afterClass(declarationTokens,class1);
				break;
			}
			}
			break;
		}
		case _static:{
			class1.setIsStatic(true);
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _final:{
				class1.setIsFinal(true);
				switch(getEnumForFoundKeyWord(declarationTokens)){
				case _class:
					afterClass(declarationTokens,class1);
					break;
				}
				break;
			}
			case _class:{
				afterClass(declarationTokens,class1);
				break;
			}
			}
			break;
		}
		case _abstract:{
			class1.setIsAbstract(true);
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _class:{
				afterClass(declarationTokens,class1);
				break;
			}
			}
			break;
		}
		case _class:{
			afterClass(declarationTokens,class1);
			break;
		}
		}
	}
	private void detectInterfaceExplicitly(ArrayList<TokensClass> declarationTokens,OOClass class1){
		class1.setIsInterface(true);
		class1.setAccessModifier(getAccessModifier(declarationTokens));
		switch(getEnumForFoundKeyWord(declarationTokens)){
		case _abstract:{
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _interface:{
				afterInterface(declarationTokens,class1);
				break;
			}
			}
			break;
		}
		case _interface:{
			afterInterface(declarationTokens,class1);
			break;
		}
		}
	}
	private void afterInterface (ArrayList<TokensClass> declarationTokens,OOClass class1) {
		class1.setName(declarationTokens.get(0).getName());
		declarationTokens.remove(0);
		switch(getEnumForFoundKeyWord(declarationTokens)){
		case _lBrace:{
			return;
		}
		}
	}
	private void afterClass (ArrayList<TokensClass> declarationTokens,OOClass class1){
		class1.setName(declarationTokens.get(0).getName());
		declarationTokens.remove(0);
		switch(getEnumForFoundKeyWord(declarationTokens)){
		case _extends:
			addParentClass(declarationTokens, class1);
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _implements:
				addInterfaceToClass(declarationTokens, class1);
				switch(getEnumForFoundKeyWord(declarationTokens)){
				case _lBrace:
					//end of state machine
					return;
				}
				break;
			case _lBrace:
				//end of state machine
				return;
			}
			break;
		case _implements:
			addInterfaceToClass(declarationTokens, class1);
			switch(getEnumForFoundKeyWord(declarationTokens)){
			case _extends:
				addParentClass(declarationTokens, class1);
				switch(getEnumForFoundKeyWord(declarationTokens)){
				case _lBrace:
					//end of state machine
					return;
				}
				break;
			case _lBrace:
				//end of state machine
				return;
			}
			break;
		case _lBrace:
			//end of state machine
			return;
		}
	}	
	private declarationKeyWords getEnumForFoundKeyWord(ArrayList<TokensClass> declarationTokens){
		declarationKeyWords keywords[] = declarationKeyWords.values();
		for(int i=0 ; i<keywords.length ; i++){
			if(keywords[i].getName().equals( declarationTokens.get(0).getName() )){
				declarationTokens.remove(0);//altering the referenced token
				return keywords[i]; // i resorted to this technique to avoid passing by reference 
				//(which will cause errors because the value referenced is altered) , get it ? 
			}
		}
		return null;//this line of code will only be reached if the given keyword is not found
	}
	private Map<String,Boolean> determineWhetherItsClassOrInterFace(ArrayList<TokensClass> declarationTokens){
		Map<String,Boolean> map = new HashMap<String,Boolean>();
		{//initialization
			map.put("itIsAClass", false);
			map.put("itIsAnInterface", false);
		}//end initialization

		for(int i=0;i<declarationTokens.size();i++){
			if( declarationTokens.get(i).getName().equals("class") ){
				map.put("itIsAClass",true);
				return map;
			}
			else if(declarationTokens.get(i).getName().equals("interface")){
				map.put("itIsAnInterface",true);
				return map;
			}
		}
		return map;//neither -> false,false
	}
	private String getAccessModifier(ArrayList<TokensClass> declarationTokens){
		String foundAccessModifier=null;
		// TODO: private is permitted for inner classes only (class inside class) - fine, I added it :D
		String accessModifiers[] = {"public","default","private"};//private is not permitted
		boolean accessModifierSpecified=false;
		for(String accessModifier : accessModifiers){
			if(declarationTokens.get(0).getName().equals(accessModifier)){
				foundAccessModifier = accessModifier;
				accessModifierSpecified=true;
				declarationTokens.remove(0);
			}
		}
		if(accessModifierSpecified==false){
			foundAccessModifier="default";
		}
		return foundAccessModifier;
	}
	private void addInterfaceToClass(ArrayList<TokensClass> declarationTokens,OOClass class1){
		ArrayList<OOClass> interfaces = new ArrayList<OOClass>();
		interfaces.add(new OOClass());
		interfaces.get(0).setIsInterface(true);
		interfaces.get(0).setName(declarationTokens.get(0).getName());
		declarationTokens.remove(0);
		//get interface which this class implements
		int counter=0;
		while(declarationTokens.get(0).getName().equals(",")){
			//pop another interface name
			counter++;
			declarationTokens.remove(0);
			interfaces.add(new OOClass());
			interfaces.get(counter).setIsInterface(true);
			interfaces.get(counter).setName(declarationTokens.get(0).getName());
			declarationTokens.remove(0);
		}
		while(!interfaces.isEmpty()){
			class1.addInterface(interfaces.get(0));
			interfaces.remove(0);
		}
	}
	private void addParentClass(ArrayList<TokensClass> declarationTokens,OOClass class1){
		OOClass parentClass = new OOClass();
		parentClass.setName(declarationTokens.get(0).getName());
		class1.setParentClass(parentClass);
		declarationTokens.remove(0);
	}


	private OOAttribute detectAttribute(ArrayList<TokensClass> subArray) {
		ArrayList<TokensClass>allowedNextTokens = new ArrayList<TokensClass>();
		// ArrayList<TokensClass> arrHistoryTokens = new
		// ArrayList<TokensClass>();
		TokensClass prevToken = null;
		TokensClass nextToken = null;
		allowedNextTokens.addAll(arrAccessModifiers);
		allowedNextTokens.addAll(arrTypes);
		//String logicalPrevToken = null;
		OOAttribute detectedAttribute = new OOAttribute();
		OOClass Classes =new OOClass();
		// OOMethod methodParameter = null;
		for (TokensClass currentToken : subArray) {

			if(subArray.indexOf(currentToken)+1< subArray.size())
				nextToken = subArray.get(subArray.indexOf(currentToken)+1);
			if(subArray.indexOf(currentToken)-1 !=-1)
				prevToken = subArray.get(subArray.indexOf(currentToken)-1);

			if(!equalsOneOf(currentToken,allowedNextTokens))
			{
				System.err.println("Attribute: Unexpected sequence of tokens: "+currentToken.getType()+"\n");
				return null;
			}

			if (equalsOneOf(currentToken, arrAccessModifiers)) {
				detectedAttribute.setAccessModifier(currentToken.getType());
				// Set allowed next tokens
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "FINAL"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
				allowedNextTokens.addAll(arrTypes);
				prevToken = currentToken;
				continue;
			} else if (currentToken.getType().equals("FINAL")) {
				detectedAttribute.setIsFinal(true);
				// Set allowed next tokens
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
				allowedNextTokens.addAll(arrTypes);
				prevToken = currentToken;
				continue;
			} else if (currentToken.getType().equals("STATIC")) {
				detectedAttribute.setIsStatic(true);
				// Set allowed next tokens
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "FINAL"));
				allowedNextTokens.addAll(arrTypes);
				prevToken = currentToken;
				continue;
			} else if (equalsOneOf(currentToken, arrTypes)) 
			{
				if(prevToken!=null)
				{
					if (prevToken.getType().equals("STATIC")
							|| prevToken.getType().equals("FINAL")
							|| equalsOneOf(prevToken, arrAccessModifiers)
					) 
					{
						// Data Type
						if (!nextToken.getType().equals("EQ")
								&& !equalsOneOf(prevToken, arrTypes)) 
						{
							OOClass tmpCls = new OOClass();
							tmpCls.setName(currentToken.getName());
							tmpCls.isLinked=false;
							detectedAttribute.setType(tmpCls);
						}
					}
					// Attribute Name
					else if (!prevToken.getType().equals("EQ") &&
							(nextToken.getType().equals("SEMICOLON")
									|| nextToken.getType().equals("EQ")
							)
					) 
					{
						detectedAttribute.setName(currentToken.getName());
					}
				}
				else
				{
					if (!nextToken.getType().equals("EQ")) 
					{
						OOClass tmpCls = new OOClass();
						tmpCls.setName(currentToken.getName());
						tmpCls.isLinked=false;
						detectedAttribute.setType(tmpCls);
					}
				}
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "EQ"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "SEMICOLON"));
				prevToken = currentToken;
				continue;
			} else if (currentToken.getType().equals("EQ")) {

				// Attribute Value
				// TODO: we are not supporting "CLASS object = anotherObject;" yet
				if(!nextToken.getType().equals("NEW")
						&& prevToken.getType().equals("IDENTIFIER")){
					detectedAttribute.setValue(nextToken.getName());
				}
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "SEMICOLON"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("NEW")){
				// Attribute Type (Object type)
				if(nextToken.getType().equals("IDENTIFIER"))
					detectedAttribute.setValue(nextToken.getName());
				else
					System.err.println("Wrong token after NEW: "+nextToken.getName() + "\n");
				//TODO
				//Need to check if the associated class is not pending, available in OOModel
				//Problem: Associated classes need to be in a list to check all of them
				// TODO: ClassLinker module will be responsible of linking classes together in the whole OOModel
				if(nextToken.getType().equals(Classes.getName())){
					//AssocClass.setAssociatedClass(nextToken.getName());
					//Problem: Function setAssociatedClass requires an object from the class not a string	
				}else{
					//send to pending this class	
				}
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("LPAREN")){
				//while(nextToken.getType().equals("RPAREN")|| nextToken.getType().equals("COMMA")){
				//TODO
				//send arguments 
				//}
				allowedNextTokens.clear();
				//TODO: we do not support: "CLASS object = new CLASS(new CLASS2());" yet
				allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "RPAREN"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("RPAREN")){
				// Set allowed next tokens
				allowedNextTokens.clear();
				//TODO: we do not support: "CLASS object = new CLASS(new CLASS2());" yet
				allowedNextTokens.add(new TokensClass(0, 0, "", "SEMICOLON"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("IDENTIFIER")){
				// Set allowed next tokens
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "LPAREN"));
				allowedNextTokens.add(new TokensClass(0, 0, "", "COMMA"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("COMMA")){
				// Set allowed next tokens
				allowedNextTokens.clear();
				allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("LBRACE")){
				// Set allowed next tokens
				allowedNextTokens.clear();
				prevToken = currentToken;
				continue;
			}else if(currentToken.getType().equals("SEMICOLON")){
				// Set allowed next tokens
				allowedNextTokens.clear();
				prevToken = currentToken;
				continue;
			}else {
				System.err.println("Unidentified Token Type: "
						+ currentToken.getType() + "\n");
				return null;
			}
		}

		return detectedAttribute;
	}

	private enum MethodDetectStateName
	{
		START,ACCESSMOD,SPECIALMODS,RETTYPE,METHNAME,METHPARAMS,END
	}

	private boolean handleMethodAccessModifier()
	{
		if(mds.currentState==MethodDetectStateName.START)
		{
			if(equalsOneOf(mds.currentToken, arrAccessModifiers))
			{
				mds.currentState=MethodDetectStateName.ACCESSMOD;
				mds.detectedMethod.setAccessModifier(mds.currentToken.getType());
				// Set allowed next tokens
				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "FINAL"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "ABSTRACT"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "CONCRETE"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "SYNCHRONIZED"));
				mds.allowedNextTokens.addAll(arrReturnTypes);
				return true;
			}
		}
		return false;
	}

	private boolean handleMethodFinal()
	{
		if(mds.currentToken.getType().equals("FINAL"))
		{
			mds.currentState=MethodDetectStateName.SPECIALMODS;
			mds.detectedMethod.setIsFinal(true);
			// Set allowed next tokens

			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
			mds.allowedNextTokens.addAll(arrTypes);
			return true;
		}
		return false;
	}

	private boolean handleMethodStatic()
	{
		if(mds.currentToken.getType().equals("STATIC"))
		{
			mds.currentState=MethodDetectStateName.SPECIALMODS;
			mds.detectedMethod.setIsStatic(true);
			// Set allowed next tokens

			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "FINAL"));
			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "SYNCHRONIZED"));
			mds.allowedNextTokens.addAll(arrTypes);
			return true;
		}
		return false;
	}

	private boolean handleMethodAbstract()
	{
		if(mds.currentToken.getType().equals("ABSTRACT"))
		{
			mds.currentState=MethodDetectStateName.SPECIALMODS;
			mds.detectedMethod.setIsAbstract(true);
			// Set allowed next tokens


			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "SYNCHRONIZED"));
			mds.allowedNextTokens.addAll(arrTypes);
			return true;
		}
		return false;
	}

	private boolean handleMethodConcrete()
	{
		if(mds.currentToken.getType().equals("CONCRETE"))
		{
			mds.currentState=MethodDetectStateName.SPECIALMODS;
			//TODO: Concrete????
			System.err.println("Concrete?!\n");


			mds.detectedMethod.setAccessModifier("CONCRETE");

			return true;
		}
		return false;
	}

	private boolean handleMethodSynchronized()
	{
		if(mds.currentToken.getType().equals("SYNCHRONIZED"))
		{
			mds.currentState=MethodDetectStateName.SPECIALMODS;
			mds.detectedMethod.setIsSynchronized(true);

			// Set allowed next tokens

			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "ABSTRACT"));
			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
			mds.allowedNextTokens.addAll(arrTypes);
			return true;
		}
		return false;
	}

	private boolean handleMethodReturnType()
	{
		/*
		 * if we came from state:
		 * start or access modifier or special modifiers
		 * AND
		 * the next token is an Identifier (method name)
		 */
		if(mds.currentState==MethodDetectStateName.START ||
				mds.currentState==MethodDetectStateName.ACCESSMOD ||
				mds.currentState==MethodDetectStateName.SPECIALMODS)
		{
			if(equalsOneOf(mds.currentToken, arrReturnTypes) &&
					mds.nextToken.getType().equals("IDENTIFIER"))
			{
				mds.currentState=MethodDetectStateName.RETTYPE;
				OOClass tmpCls = new OOClass();
				tmpCls.setName(mds.currentToken.getName());
				tmpCls.isLinked=false;
				mds.detectedMethod.returnType = tmpCls;
				OOClass tmpRetTypeClass = new OOClass();
				tmpRetTypeClass.setName(mds.currentToken.getName());
				mds.methodDependeeClasses.add(tmpRetTypeClass);
				// Set allowed next tokens

				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "IDENTIFIER"));
				return true;
			}
		}
		return false;
	}

	private boolean handleMethodName()
	{
		/*
		 * if we came from state:
		 * retrun type or access modifier or special modifiers
		 * AND
		 * the next token is a '('
		 */
		if(mds.currentState==MethodDetectStateName.RETTYPE ||
				mds.currentState==MethodDetectStateName.ACCESSMOD ||
				mds.currentState==MethodDetectStateName.SPECIALMODS)
		{
			if(mds.currentToken.getType().equals("IDENTIFIER") &&
					mds.nextToken.getType().equals("LPAREN"))
			{
				mds.currentState=MethodDetectStateName.METHNAME;
				mds.detectedMethod.setName(mds.currentToken.getName());

				// Set allowed next tokens

				mds.allowedNextTokens.add(new TokensClass(0, 0, "", "LPAREN"));
				return true;
			}
		}
		return false;
	}

	private boolean handleMethodLParen()
	{
		if(mds.currentState==MethodDetectStateName.METHNAME)
		{
			mds.currentState=MethodDetectStateName.METHPARAMS;
			mds.allowedNextTokens.addAll(arrTypes);
			mds.allowedNextTokens.add(new TokensClass(0, 0, "", "RPAREN"));
			return true;
		}
		return false;
	}

	private boolean handleMethodDefinitionEnd()
	{
		if(mds.currentState==MethodDetectStateName.END)
		{
			if(mds.nextToken.getType().equals("LBRACE")||
					mds.nextToken.getType().equals("SEMICOLON"))
			{
				//				mds.allowedNextTokens.addAll(arrTypes);
				mds.allowedNextTokens.add(new TokensClass(0, 0, "{", "LBRACE"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, ";", "SEMICOLON"));
			}
			return true;
		}
		return false;
	}

	private ArrayList<TokensClass> paramInfo = new ArrayList<TokensClass>();
	private boolean handleMethodParameter()
	{
		if(mds.currentState==MethodDetectStateName.METHPARAMS)
		{
			if(!mds.currentToken.getType().equals("COMMA") &&
					!mds.currentToken.getType().equals("RPAREN"))
			{
				paramInfo.add(mds.currentToken); // add to the parameter subarray
				mds.allowedNextTokens.addAll(arrTypes);
				mds.allowedNextTokens.add(new TokensClass(0, 0, ",", "COMMA"));
				mds.allowedNextTokens.add(new TokensClass(0, 0, ")", "RPAREN"));
			}
			else
			{
				if(paramInfo.size()>0)
				{
					// use detectAttribute to detect the parameter
					paramInfo.add(new TokensClass(0, 0, ";", "SEMICOLON"));
					OOAttribute tmpParam =detectAttribute(paramInfo);
					if(tmpParam!=null)
						mds.detectedMethod.parameters.add(tmpParam);
					paramInfo.clear();
				}
				// if we still have more parameters
				if(mds.currentToken.getType().equals("COMMA"))
				{
					mds.allowedNextTokens.addAll(arrTypes);
				}
				else // we have no more parameters
				{
					mds.currentState=MethodDetectStateName.END;
					mds.allowedNextTokens.add(new TokensClass(0, 0, "{", "LBRACE"));
					mds.allowedNextTokens.add(new TokensClass(0, 0, ";", "SEMICOLON"));
				}
			}
			return true;
		}
		return false;
	}

	private class MethodDetectionState
	{
		public ArrayList<TokensClass> allowedNextTokens;
		@SuppressWarnings("unused")
		public TokensClass prevToken = null;
		public TokensClass nextToken = null;
		public TokensClass currentToken = null;

		public ArrayList<OOClass> methodDependeeClasses=null;
		public OOMethod detectedMethod=null;

		public MethodDetectStateName currentState;
	}
	private MethodDetectionState mds;
	private OOMethod detectMethod(ArrayList<TokensClass> subArray){
		mds = new MethodDetectionState();
		mds.allowedNextTokens = new ArrayList<TokensClass>();
		mds.methodDependeeClasses = new ArrayList<OOClass>();

		/**
		 *  Start Tokens, they must be added to the array of allowed tokens
		 *  for the first token to be valid
		 */
		mds.allowedNextTokens.add(new TokensClass(0, 0, "", "FINAL"));
		mds.allowedNextTokens.add(new TokensClass(0, 0, "", "STATIC"));
		mds.allowedNextTokens.add(new TokensClass(0, 0, "", "ABSTRACT"));
		mds.allowedNextTokens.add(new TokensClass(0, 0, "", "CONCRETE"));
		mds.allowedNextTokens.add(new TokensClass(0, 0, "", "SYNCHRONIZED"));
		mds.allowedNextTokens.addAll(arrAccessModifiers);

		mds.detectedMethod = new OOMethod();

		mds.currentState=MethodDetectStateName.START;
		for(TokensClass currentToken:subArray)
		{
			mds.currentToken=currentToken;
			if(subArray.indexOf(currentToken)+1< subArray.size())
				mds.nextToken = subArray.get(subArray.indexOf(currentToken)+1);
			if(subArray.indexOf(currentToken)-1 !=-1)
				mds.prevToken = subArray.get(subArray.indexOf(currentToken)-1);

			if(!equalsOneOf(currentToken,mds.allowedNextTokens))
			{
				System.err.println("Method: Unexpected sequence of tokens: "+currentToken.getType()+"\n");
				return null;
			}
			mds.allowedNextTokens.clear();
			if(mds.currentState==MethodDetectStateName.START)
			{
				if(handleMethodAccessModifier())
					continue;
				if(handleMethodFinal())
					continue;
				if(handleMethodStatic())
					continue;
				if(handleMethodAbstract())
					continue;
				if(handleMethodConcrete())
					continue;
				if(handleMethodSynchronized())
					continue;
			}
			else if(mds.currentState==MethodDetectStateName.ACCESSMOD)
			{
				if(handleMethodFinal())
					continue;
				if(handleMethodStatic())
					continue;
				if(handleMethodAbstract())
					continue;
				if(handleMethodConcrete())
					continue;
				if(handleMethodSynchronized())
					continue;
				if(handleMethodReturnType())
					continue;
				if(handleMethodName())
					continue;
			}
			else if(mds.currentState==MethodDetectStateName.SPECIALMODS)
			{
				if(handleMethodReturnType())
					continue;
				if(handleMethodName())
					continue;
			}
			else if(mds.currentState==MethodDetectStateName.RETTYPE)
			{
				if(handleMethodName())
					continue;
				else
					System.err.println("no methodname after return type\n");
			}
			else if(mds.currentState==MethodDetectStateName.METHNAME)
			{
				if(handleMethodLParen())
					continue;
				else
					System.err.println("no '(' after method name\n");
			}
			else if(mds.currentState==MethodDetectStateName.METHPARAMS)
			{
				if(handleMethodParameter())
					continue;

			}
			else if(mds.currentState==MethodDetectStateName.END)
			{
				if(handleMethodDefinitionEnd())
					continue;
			}
		}
		return mds.detectedMethod;
	}

	// For Testing ONLY:
//	public static void main(String args[]) {
//
//		//testDetectAttribute();
//		testModelGeneratorAll();
//	}

	public static void testModelGeneratorAll()
	{
		OOModel ooModel = new OOModel();
		ModelGenerator mg = new ModelGenerator(ooModel);
		Scanner scanner = new Scanner();
		ArrayList<TokensClass> arr = null;
		try {
			arr = scanner.scan(new java.io.FileReader("C:\\TestClass.java"));
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		mg.generateModel(arr);
		System.out.println("Testing Model Generation!\n");
	}

	public static void testDetectMethod()
	{
		// Testing Method Detection
		OOModel ooModel = new OOModel();
		ModelGenerator mg = new ModelGenerator(ooModel);
		ArrayList<TokensClass> arr = new ArrayList<TokensClass>();
		//arr.add(new TokensClass(0, 0, "public", "PUBLIC"));
		arr.add(new TokensClass(0, 0, "final", "FINAL"));
		arr.add(new TokensClass(0, 0, "static", "STATIC"));
		arr.add(new TokensClass(0, 0, "RetClass", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "myMethod", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "(", "LPAREN"));
		arr.add(new TokensClass(0, 0, "ParamClass", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "x", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, ")", "RPAREN"));
		arr.add(new TokensClass(0, 0, "{", "LBRACE"));


		@SuppressWarnings("unused")
		OOMethod oom = mg.detectMethod(arr);
		System.out.println("Testing OOMethod Detection!\n");
	}

	public static void testDetectAttribute()
	{
		// Testing Attribute Detection
		OOModel ooModel = new OOModel();
		ModelGenerator mg = new ModelGenerator(ooModel);
		ArrayList<TokensClass> arr = new ArrayList<TokensClass>();
		arr.add(new TokensClass(0, 0, "public", "PUBLIC"));
		arr.add(new TokensClass(0, 0, "final", "FINAL"));
		arr.add(new TokensClass(0, 0, "static", "STATIC"));
		arr.add(new TokensClass(0, 0, "AttrClass", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "myAttribute", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "=", "EQ"));
		//arr.add(new TokensClass(0, 0, "5", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "new", "NEW"));
		arr.add(new TokensClass(0, 0, "AttrClass2", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, "(", "LPAREN"));
		arr.add(new TokensClass(0, 0, "x", "IDENTIFIER"));
		arr.add(new TokensClass(0, 0, ")", "RPAREN"));
		arr.add(new TokensClass(0, 0, ")", "SEMICOLON"));
		arr.add(new TokensClass(0, 0, "{", "LBRACE"));


		@SuppressWarnings("unused")
		OOAttribute ooa = mg.detectAttribute(arr);
		System.out.println("Testing OOAttribute Detection!\n");
	}

	private boolean equalsOneOf(TokensClass inputToken,ArrayList<TokensClass> arr)
	{
		for(TokensClass token:arr)
		{
			if(inputToken.getType().equals(token.getType()))
				return true;
		}
		return false;
	}

	private void linkModel()
	{
		// TODO: we need to support an "import" array, to be able to diff. between 
		// classes with the same name in diff. pkgs.
		for(OOClass ooClass1:ooModel.classes)
		{
			for(OOClass ooClass2:ooModel.classes)
			{
				ooClass1.linkWithClass(ooClass2);
			}
			
			for(OOPackage pkg:ooModel.packages)
			{
				ooClass1.linkWithPackage(pkg);
			}
		}
	}

}
